package com.gwt.dispatcher.dispatcher.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.gwt.dispatcher.dispatcher.shared.Action;
import com.gwt.dispatcher.dispatcher.shared.Result;

public class CacheContext {
    
    private Map<String, Result> cachedValues = new HashMap<String, Result>();
    private Map<String, List<AsyncCallback<? extends Result>>> inProgress = new HashMap<String, List<AsyncCallback<? extends Result>>>();

    
    public <T extends Result> void getValue(final String uniqueId, Action<T> action, AsyncCallback<T> callback) {
        
        // if it's cached, then serve it immediately
        if ( cachedValues.containsKey(uniqueId) ) {
            callback.onSuccess( (T) cachedValues.get(uniqueId) );
            return;
        }
        

        // if a query is in progress, queued it up and return immediately
        if ( inProgress.containsKey(uniqueId) ) {
            
            List<AsyncCallback<? extends Result>> queued = inProgress.get(uniqueId);
            
            queued.add(callback);
            
            return;
        }
        
        
        // add this request to the queue
        List<AsyncCallback<? extends Result>> queue = new ArrayList<AsyncCallback<? extends Result>>();
        queue.add(callback);
        
        // mark it as in progress
        inProgress.put(uniqueId, queue);
        
            
        // else, trigger the query
        Dispatcher.doExecution(action, new AsyncCallback<T>() {

            public void onFailure(Throwable caught) {
                
                /**
                 * These are the steps:
                 *  
                 *  2. Notify all queued callers
                 *  3. Remove it from the 'inProgress' queue
                 */
                
                for (AsyncCallback<? extends Result> asyncCallback : inProgress.get(uniqueId) ) {
                    
                    try {
                        asyncCallback.onFailure(caught);
                    }
                    catch(Exception e) {
                    }
                    
                }
                
                inProgress.remove(uniqueId);             
            }

            public void onSuccess(T result) {
                
                /**
                 * These are the steps:
                 *  
                 *  1. Add it to the cached values
                 *  2. Notify all queued callers
                 *  3. Remove it from the 'inProgress' queue
                 */
                cachedValues.put(uniqueId, result);
                
                for (AsyncCallback<? extends Result> asyncCallback : inProgress.get(uniqueId) ) {
                    
                    try {
                        ((AsyncCallback<T>) asyncCallback).onSuccess(result);
                    }
                    catch(Exception e) {
                    }
                    
                }
                
                inProgress.remove(uniqueId);
                
            }
            
        });
        
        
    }

}
